跳至主要内容

Counting Tilings

題目

有一個 n×mn\times m 的棋盤

你的任務是求出有幾種方式可以用 1×21\times 22×12\times 1 的骨牌鋪滿它

輸入

輸入兩個正整數 nnmm,分別代表棋盤的長寬。(1n101 \le n \le 101m10001 \le m \le 1000

輸出

輸出一個正整數,為答案模 109+710^9 + 7 的值

範例測資

Input : 
4 7

Output :
781

想法

輪廓線 DP

狀態定義

dpi,maskdp_{i, mask} 為第 11 排到第 i1i - 1 排全滿,第 ii 排已填完 maskmask 的方法數

狀態轉移

對於從第 i1i-1 排轉移到第 ii

  1. 先處理 1×21\times 2 的,因為完成這一步後不能在前一行留空,所以轉移式為 dpi,mask=dpi1,maskdp_{i, mask} = dp_{i - 1, \sim mask} ("~"表位元反轉)
  2. 處理 2×12\times 1 的 對於一個狀態,若加入一個 dominodomino 可以從同一排轉移,即 dpi,mask=dpi,mask+dpi,maskdominodp_{i, mask} = dp_{i, mask}+ dp_{i, mask-domino} 要注意為避免不同擺放順序被計為不同,要先枚舉 "骨牌的位置" 如果先枚舉 " maskmask ",即範例中交換 for kfor j,會WA

P.S.範例用了滾動DP,但這題不用滾動DP也是可以的

範例程式碼

C++ 範例
#include<bits/stdc++.h>
#define int long long
#define IO ios_base::sync_with_stdio(0), cin.tie(0)
using namespace std;
const int mod = 1e9+7;

signed main(){
IO;
int n,m;
cin >> n >> m;
vector<int> dp(1 << n, 0);
int mask = (1 << n) - 1;
dp[mask] = 1;
for(int i = 1; i <= m; i++) {
for(int j = 0; j * 2 < mask; j++) {
swap(dp[j] ,dp[mask ^ j]);
}
for(int k = 0; k < n - 1; k++) {
int w = 3 << k;
for(int j = 0; j <= mask; j++) {
if((j & w) == w) {
dp[j] = (dp[j] + dp[j ^ w]) % mod;
}
}
}
}
cout << dp[mask] << "\n";
}